package business.client;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * 简化的Raft KV客户端示例
 * 
 * <p>展示如何在Java应用中使用Raft分布式键值存储。提供了简单易用的API接口，
 * 隐藏了分布式系统的复杂性，让业务开发者可以专注于业务逻辑。</p>
 * 
 * <p><b>主要特性：</b></p>
 * <ul>
 *   <li>自动故障转移：节点不可达时自动尝试其他节点</li>
 *   <li>Leader重定向：写操作自动重定向到当前Leader</li>
 *   <li>简单API：提供set/get/delete三个基本操作</li>
 *   <li>业务场景支持：配置中心、服务注册等应用示例</li>
 * </ul>
 * 
 * @author Raft Implementation Team
 * @version 1.0
 * @since 1.0
 */
public class SimpleKVClient {
    
    /** 集群节点URL列表 */
    private final List<String> nodeUrls;
    
    /**
     * 构造客户端
     * 
     * @param nodeUrls 集群节点URL列表，格式如: http://localhost:8001
     */
    public SimpleKVClient(List<String> nodeUrls) {
        this.nodeUrls = new ArrayList<>(nodeUrls);
    }
    
    /**
     * 设置键值对
     * 
     * <p>这是一个写操作，会自动路由到当前Leader节点。如果当前节点不是Leader，
     * 会收到307重定向响应，客户端自动重定向到Leader。</p>
     * 
     * @param key 键，不能为空
     * @param value 值，不能为空
     * @return 操作是否成功
     */
    public boolean set(String key, String value) {
        for (String nodeUrl : nodeUrls) {
            try {
                String url = nodeUrl + "/kv/" + key;
                HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
                conn.setRequestMethod("PUT");
                conn.setDoOutput(true);
                conn.setConnectTimeout(5000);
                conn.setReadTimeout(10000);
                
                try (OutputStream os = conn.getOutputStream()) {
                    os.write(value.getBytes(StandardCharsets.UTF_8));
                }
                
                int responseCode = conn.getResponseCode();
                if (responseCode == 200) {
                    return true;
                } else if (responseCode == 307) {
                    // 重定向到Leader
                    String leaderUrl = conn.getHeaderField("Location");
                    if (leaderUrl != null) {
                        return makeRequest(leaderUrl, "PUT", value) == 200;
                    }
                }
            } catch (Exception e) {
                System.err.println("节点 " + nodeUrl + " 不可达: " + e.getMessage());
            }
        }
        return false;
    }
    
    /**
     * 获取键的值
     * 
     * <p>这是一个读操作，可以从任意节点读取数据。客户端会依次尝试所有节点，
     * 直到成功获取数据或所有节点都不可达。</p>
     * 
     * @param key 键，不能为空
     * @return 键对应的值，如果键不存在返回null
     */
    public String get(String key) {
        for (String nodeUrl : nodeUrls) {
            try {
                String url = nodeUrl + "/kv/" + key;
                HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
                conn.setRequestMethod("GET");
                conn.setConnectTimeout(5000);
                conn.setReadTimeout(10000);
                
                int responseCode = conn.getResponseCode();
                if (responseCode == 200) {
                    return readResponse(conn);
                } else if (responseCode == 404) {
                    return null; // 键不存在
                }
            } catch (Exception e) {
                System.err.println("节点 " + nodeUrl + " 不可达: " + e.getMessage());
            }
        }
        return null;
    }
    
    /**
     * 删除键值对
     * 
     * <p>这是一个写操作，需要通过Leader执行。处理逻辑与set操作类似，
     * 会自动重定向到Leader节点。</p>
     * 
     * @param key 要删除的键，不能为空
     * @return 操作是否成功
     */
    public boolean delete(String key) {
        for (String nodeUrl : nodeUrls) {
            try {
                String url = nodeUrl + "/kv/" + key;
                HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
                conn.setRequestMethod("DELETE");
                conn.setConnectTimeout(5000);
                conn.setReadTimeout(10000);
                
                int responseCode = conn.getResponseCode();
                if (responseCode == 200) {
                    return true;
                } else if (responseCode == 307) {
                    // 重定向到Leader
                    String leaderUrl = conn.getHeaderField("Location");
                    if (leaderUrl != null) {
                        return makeRequest(leaderUrl, "DELETE", null) == 200;
                    }
                }
            } catch (Exception e) {
                System.err.println("节点 " + nodeUrl + " 不可达: " + e.getMessage());
            }
        }
        return false;
    }
    
    /**
     * 检查键是否存在
     * 
     * @param key 要检查的键
     * @return 键是否存在
     */
    public boolean exists(String key) {
        return get(key) != null;
    }
    
    /**
     * 发送HTTP请求的辅助方法
     */
    private int makeRequest(String url, String method, String body) {
        try {
            HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
            conn.setRequestMethod(method);
            conn.setConnectTimeout(5000);
            conn.setReadTimeout(10000);
            
            if (body != null) {
                conn.setDoOutput(true);
                try (OutputStream os = conn.getOutputStream()) {
                    os.write(body.getBytes(StandardCharsets.UTF_8));
                }
            }
            
            return conn.getResponseCode();
        } catch (Exception e) {
            return -1;
        }
    }
    
    /**
     * 读取HTTP响应的辅助方法
     */
    private String readResponse(HttpURLConnection conn) throws IOException {
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
            return sb.toString();
        }
    }
    
    /**
     * 使用示例和测试
     */
    public static void main(String[] args) throws InterruptedException {
        // 创建客户端，连接到3节点集群
        List<String> nodes = Arrays.asList(
            "http://localhost:8001",
            "http://localhost:8002", 
            "http://localhost:8003"
        );
        SimpleKVClient client = new SimpleKVClient(nodes);
        
        System.out.println("=== Raft KV客户端示例 ===\n");
        
        // 1. 基本操作演示
        System.out.println("1. 基本操作演示:");
        client.set("user:1001", "张三");
        client.set("config:db", "mysql://localhost:3306");
        System.out.println("   设置: user:1001 = 张三");
        System.out.println("   设置: config:db = mysql://localhost:3306");
        
        // 等待数据同步
        Thread.sleep(1000);
        
        System.out.println("\n2. 读取数据:");
        System.out.println("   user:1001 = " + client.get("user:1001"));
        System.out.println("   config:db = " + client.get("config:db"));
        System.out.println("   unknown_key = " + client.get("unknown_key"));
        
        // 3. 配置中心场景演示
        System.out.println("\n3. 配置中心场景:");
        client.set("app:web:database.host", "192.168.1.100");
        client.set("app:web:database.port", "3306");
        client.set("app:web:database.name", "myapp");
        client.set("app:web:redis.host", "192.168.1.200");
        client.set("app:web:redis.port", "6379");
        
        System.out.println("   应用配置已设置:");
        System.out.println("     数据库主机: " + client.get("app:web:database.host"));
        System.out.println("     数据库端口: " + client.get("app:web:database.port"));
        System.out.println("     数据库名称: " + client.get("app:web:database.name"));
        System.out.println("     Redis主机: " + client.get("app:web:redis.host"));
        System.out.println("     Redis端口: " + client.get("app:web:redis.port"));
        
        // 4. 服务注册与发现场景
        System.out.println("\n4. 服务注册与发现:");
        client.set("service:user-service:node1", "{\"host\":\"192.168.1.10\",\"port\":8080,\"weight\":100}");
        client.set("service:user-service:node2", "{\"host\":\"192.168.1.11\",\"port\":8080,\"weight\":100}");
        client.set("service:order-service:node1", "{\"host\":\"192.168.1.20\",\"port\":8081,\"weight\":100}");
        client.set("service:payment-service:node1", "{\"host\":\"192.168.1.30\",\"port\":8082,\"weight\":100}");
        
        System.out.println("   已注册的服务实例:");
        System.out.println("     user-service:node1 = " + client.get("service:user-service:node1"));
        System.out.println("     user-service:node2 = " + client.get("service:user-service:node2"));
        System.out.println("     order-service:node1 = " + client.get("service:order-service:node1"));
        System.out.println("     payment-service:node1 = " + client.get("service:payment-service:node1"));
        
        // 5. 分布式锁场景
        System.out.println("\n5. 分布式锁场景:");
        String lockKey = "lock:critical-resource";
        String lockValue = "client-" + System.currentTimeMillis();
        
        if (client.set(lockKey, lockValue)) {
            System.out.println("   成功获取锁: " + lockKey + " = " + lockValue);
            
            // 模拟业务处理
            System.out.println("   执行关键业务逻辑...");
            Thread.sleep(2000);
            
            // 释放锁
            if (client.delete(lockKey)) {
                System.out.println("   成功释放锁: " + lockKey);
            }
        } else {
            System.out.println("   获取锁失败: " + lockKey);
        }
        
        // 6. 键存在性检查
        System.out.println("\n6. 键存在性检查:");
        System.out.println("   user:1001 存在: " + client.exists("user:1001"));
        System.out.println("   nonexistent 存在: " + client.exists("nonexistent"));
        
        // 7. 删除操作演示
        System.out.println("\n7. 删除操作:");
        boolean deleted = client.delete("user:1001");
        System.out.println("   删除 user:1001: " + (deleted ? "成功" : "失败"));
        System.out.println("   验证删除: user:1001 = " + client.get("user:1001"));
        
        System.out.println("\n=== 客户端演示完成 ===");
        System.out.println("\n提示:");
        System.out.println("  - 确保Raft集群已启动: ./start_cluster.sh");
        System.out.println("  - 查看集群状态: curl http://localhost:8001/status");
        System.out.println("  - 停止集群: ./stop_cluster.sh");
    }
} 